using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Text.RegularExpressions;

namespace ContractDocumentation
{
    static class XmlUtils
    {
        static XNamespace wsdl = "http://schemas.xmlsoap.org/wsdl/";
        static XNamespace xs = "http://www.w3.org/2001/XMLSchema";

        internal static XName GetXName(XElement contextElement, string prefixedName)
        {
            if (prefixedName == null) return null;
            XNamespace xNamespace;
            if (prefixedName.Contains(":")) xNamespace = contextElement.GetNamespaceOfPrefix(prefixedName.Split(':').First());
            else xNamespace = contextElement.GetDefaultNamespace();

            return xNamespace + prefixedName.Split(':').Last();
        }

        internal static double GetMinOccurs(XElement element)
        {
            var minOccurs = element.Attribute("minOccurs");
            if (minOccurs != null)
            {
                return (int)minOccurs;
            }
            else
            {
                return 1;
            }
        }

        internal static double GetMaxOccurs(XElement element)
        {
            var maxOccurs = element.Attribute("maxOccurs");
            if ((string)maxOccurs == "unbounded")
            {
                return Double.PositiveInfinity;
            }
            else if (maxOccurs != null)
            {
                return (int)maxOccurs;
            }
            else
            {
                return 1;
            }
        }

        internal static XElement GetMessageDefinition(Context context, XName messageName)
        {
            var q = from descriptionElement in context.Wsdls
                    where (string)descriptionElement.Attribute("targetNamespace") == messageName.Namespace
                    from messageElement in descriptionElement.Elements(wsdl + "message")
                    where (string)messageElement.Attribute("name") == messageName.LocalName
                    select messageElement;
            return q.SingleOrDefault();
        }

        internal static IEnumerable<XElement> GetMessageDefinitionAsSequence(Context context, XName elementName)
        {
            var element = GetMessageDefinition(context, elementName);
            if (element != null) yield return element;
        }

        internal static XElement GetElementDefinition(Context context, XName elementName)
        {
            var q = from schemaElement in context.Schemas
                    where (string)schemaElement.Attribute("targetNamespace") == elementName.Namespace
                    from complexTypeElement in schemaElement.Elements(xs + "element")
                    where (string)complexTypeElement.Attribute("name") == elementName.LocalName
                    select complexTypeElement;
            return q.SingleOrDefault();
        }

        internal static IEnumerable<XElement> GetElementDefinitionAsSequence(Context context, XName elementName)
        {
            var element = GetElementDefinition(context, elementName);
            if (element != null) yield return element;
        }

        internal static XElement GetComplexTypeDefinition(Context context, XName complexTypeName)
        {
            var q = from schemaElement in context.Schemas
                    where (string)schemaElement.Attribute("targetNamespace") == complexTypeName.Namespace
                    from complexTypeElement in schemaElement.Elements(xs + "complexType")
                    where (string)complexTypeElement.Attribute("name") == complexTypeName.LocalName
                    select complexTypeElement;
            return q.SingleOrDefault();
        }

        internal static XElement GetSimpleTypeRestriction(XElement element)
        {
            var q = from simpleTypeElement in element.Elements(xs + "simpleType")
                    from restrictionElement in simpleTypeElement.Elements(xs + "restriction")
                    select restrictionElement;
            return q.SingleOrDefault();
        }

        internal static string GetWsdlDocumentation(XElement element)
        {
            var q = from documentationElement in element.Elements(wsdl + "documentation")
                    select (string)documentationElement;
            return GetDocumentation(q);
        }

        internal static string GetXsDocumentation(XElement element)
        {
            var q = from annotationElement in element.Elements(xs + "annotation")
                    from documentationElement in annotationElement.Elements(xs + "documentation")
                    select (string)documentationElement;
            return GetDocumentation(q);
        }

        static string GetDocumentation(IEnumerable<string> documentations)
        {
            string documentation = string.Join(" ", documentations.ToArray());
            documentation = Regex.Replace(documentation, @"\s+", " ").Trim();
            if (documentation == "") return null;
            return documentation;
        }

        internal static IEnumerable<XName> GetDerivedComplexTypes(Context context, XName baseType)
        {
            var baseTypes = new List<XName>();
            baseTypes.Add(baseType);
            return GetDerivedComplexTypes(context, baseTypes).Skip(1);
        }

        static List<XName> GetDerivedComplexTypes(Context context, List<XName> baseTypes)
        {
            var q = from schemaElement in context.Schemas
                    let targetNamespace = (string)schemaElement.Attribute("targetNamespace")
                    from complexTypeElement in schemaElement.Elements(xs + "complexType")
                    let complexTypeName = XName.Get((string)complexTypeElement.Attribute("name"), targetNamespace)
                    where ! baseTypes.Contains(complexTypeName)
                    from complexContentElement in complexTypeElement.Elements(xs + "complexContent")
                    from extensionElement in complexContentElement.Elements(xs + "extension")
                    where baseTypes.Contains(GetXName(extensionElement, (string)extensionElement.Attribute("base")))
                    select complexTypeName;
            var subTypes = q.ToList();
            if (subTypes.Count > 0)
            {
                baseTypes.AddRange(subTypes);
                return GetDerivedComplexTypes(context, baseTypes);
            }
            else
            {
                return baseTypes;
            }
        }

        internal static bool IsAbstract(Context context, XName complexTypeName)
        {
            var def = GetComplexTypeDefinition(context, complexTypeName);
            return ((bool?)def.Attribute("abstract")).GetValueOrDefault();
        }

        internal static void ChangeNamespace(XElement element, IDictionary<XNamespace, XNamespace> namespaceMapping)
        {
            element.Name = XName.Get(element.Name.LocalName, namespaceMapping[element.Name.Namespace].NamespaceName);
            foreach (XElement e in element.Elements())
            {
                ChangeNamespace(e, namespaceMapping);
            }
        }
    }
}
